package info.motteke.mixivoice4j.http;

import info.motteke.mixivoice4j.Comment;
import info.motteke.mixivoice4j.Entry;
import info.motteke.mixivoice4j.MixiVoiceException;
import info.motteke.mixivoice4j.User;
import info.motteke.mixivoice4j.http.html.Home;
import info.motteke.mixivoice4j.http.html.RecentVoice;
import info.motteke.mixivoice4j.http.json.Json;
import info.motteke.mixivoice4j.http.json.JsonBasicResponse;
import info.motteke.mixivoice4j.http.json.comment.create.CreateCommentFactory;
import info.motteke.mixivoice4j.http.json.comment.create.CreateCommentRequest;
import info.motteke.mixivoice4j.http.json.comment.create.CreateCommentResponse;
import info.motteke.mixivoice4j.http.json.comment.delete.DeleteCommentFactory;
import info.motteke.mixivoice4j.http.json.comment.delete.DeleteCommentRequest;
import info.motteke.mixivoice4j.http.json.comment.get.GetCommentFactory;
import info.motteke.mixivoice4j.http.json.comment.get.GetCommentRequest;
import info.motteke.mixivoice4j.http.json.comment.get.GetCommentResponse;
import info.motteke.mixivoice4j.http.json.entry.delete.DeleteEntryFactory;
import info.motteke.mixivoice4j.http.json.entry.delete.DeleteEntryRequest;
import info.motteke.mixivoice4j.http.json.entry.delete.DeleteEntryResponse;
import info.motteke.mixivoice4j.http.json.favorite.delete.DeleteFavoriteFactory;
import info.motteke.mixivoice4j.http.json.favorite.delete.DeleteFavoriteRequest;
import info.motteke.mixivoice4j.http.json.favorite.get.GetFavoriteFactory;
import info.motteke.mixivoice4j.http.json.favorite.get.GetFavoriteRequest;
import info.motteke.mixivoice4j.http.json.favorite.get.GetFavoriteResponse;
import info.motteke.mixivoice4j.http.json.photo.get.GetPhotoThumbnailUrlFactory;
import info.motteke.mixivoice4j.http.json.photo.get.GetPhotoThumbnailUrlRequest;
import info.motteke.mixivoice4j.http.json.photo.get.GetPhotoThumbnailUrlResponse;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ContentProducer;
import org.apache.http.entity.EntityTemplate;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

/**
 * MixiとHttp通信を行うクライアントです。
 *
 * @author Y.Wakamatsu
 *
 */
class MixiHttpClient {

    private static final Log log = LogFactory.getLog(MixiHttpClient.class);

    /**
     * 文字列を返すためのハンドラーです。
     */
    private static final StringResponseHandler STRING_HANDLER;

    static {
        STRING_HANDLER = createStringHandler();
    }

    private static StringResponseHandler createStringHandler() {
        return new StringResponseHandler("JISAutoDetect");
    }

    private final HttpClient client;

    private AtomicInteger count = new AtomicInteger(0);

    public MixiHttpClient() {
        client = new DefaultHttpClient();
    }

    /**
     * ログインを行います。
     *
     * @param userId ユーザIDです。非nullである必要があります。
     * @param password パスワードです。非nullである必要があります。
     * @throws IllegalArgumentException 引数がnullの場合
     */
    public void login(String userId, String password) {
        if (userId == null) {
            throw new IllegalArgumentException("ユーザIDがnullです。");
        }
        if(password == null) {
            throw new IllegalArgumentException("パスワードがnullです。");
        }

        String response;
        try {
            HttpPost post = new HttpPost(MixiURL.login());
            List<NameValuePair> params = getList();
            params.add(new BasicNameValuePair("email", userId));
            params.add(new BasicNameValuePair("password", password));
            params.add(new BasicNameValuePair("next_url", "/home.pl"));

            post.setEntity(new UrlEncodedFormEntity(params));

            int c = count.incrementAndGet();
            log.info("request(" + c + ") : login");
            response = this.client.execute(post, STRING_HANDLER);
        } catch (IOException e) {
            throw new MixiVoiceException(e);
        }

        if (response.indexOf("メールアドレスもしくはパスワードが異なります。") != -1) {
            throw new MixiVoiceException("ログインに失敗しました。");
        }
        return;
    }

    public void logout() {
        int c = count.incrementAndGet();
        log.info("request(" + c + ") : logout");
        get(MixiURL.logout(), STRING_HANDLER);
    }

    public Home getHome() {
        int c = count.incrementAndGet();
        log.info("request(" + c + ") : home");
        return new Home(get(MixiURL.home(), STRING_HANDLER));
    }

    public RecentVoice getRecentVoice(int page, User owner) {
        String url = MixiURL.recentVoice(page, owner.getId());
        int c = count.incrementAndGet();
        log.info("request(" + c + ") : RecentVoice(" + page + ")");
        return new RecentVoice(get(url, STRING_HANDLER));
    }

    public GetCommentResponse getComments(Entry voice, String postKey, int page) {
        GetCommentFactory f = Json.getCommentFactory();
        GetCommentRequest req = f.getRequestBuilder()
                                 .entry(voice)
                                 .postKey(postKey)
                                 .limitRows(30)
                                 .limitStart(page * 30)
                                 .build();

        HttpPost post = getJsonPost(MixiURL.commentList(), req);

        int c = count.incrementAndGet();
        log.info("request(" + c + ") : getComment");
        return execute(post, jsonHandler(f.getResponseClass()));
    }

    public GetFavoriteResponse getFavorites(Entry voice, String postKey) {
        GetFavoriteFactory f = Json.getFavoriteFactory();
        GetFavoriteRequest req = f.getRequestBuilder()
                                  .entry(voice)
                                  .build();

        HttpPost post = getJsonPost(MixiURL.favoriteList(postKey), req);

        int c = count.incrementAndGet();
        log.info("request(" + c + ") : getFavorite");
        return execute(post, jsonHandler(f.getResponseClass()));
    }

    public RecentVoice createEntry(User user, String body, String postKey) {

        return createEntry(user, body, postKey, null, null);
    }

    public RecentVoice createEntry(User user, String body, String postKey, InputStream image, String imageName) {

        HttpPost post = new HttpPost(MixiURL.post());

        MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);

        try {
            Charset charset = Charset.forName("EUC-JP");
            entity.addPart("body", new StringBody(body, charset));
            entity.addPart("post_key", new StringBody(postKey, charset));
            entity.addPart("guidance_id", new StringBody("1", charset));
            entity.addPart("redirect", new StringBody("recent_voice.pl", charset));
            entity.addPart("status_message", new StringBody("", charset));
        } catch (UnsupportedEncodingException e) {
            throw new MixiVoiceException(e);
        }

        if(image != null){
            entity.addPart("photo", new InputStreamBody(image, imageName));
        }

        int c = count.incrementAndGet();
        log.info("request(" + c + ") : createEntry");
        post.setEntity(entity);
        String html = execute(post, STRING_HANDLER);

        if(log.isDebugEnabled()){
            log.debug(html);
        }

        return getRecentVoice(1, user);
    }

    public CreateCommentResponse createComment(Entry voice, String postKey, User user, String body) {
        CreateCommentFactory f = Json.createCommentFactory();
        CreateCommentRequest req = f.getRequestBuilder()
                                    .entry(voice)
                                    .user(user)
                                    .body(body)
                                    .build();

        HttpPost post = getJsonPost(MixiURL.createComment(postKey), req);

        int c = count.incrementAndGet();
        log.info("request(" + c + ") : createComment");
        return execute(post, jsonHandler(f.getResponseClass()));
    }

    public DeleteEntryResponse deleteEntry(Entry voice) {
        DeleteEntryFactory f = Json.deleteEntryFactory();

        DeleteEntryRequest req = f.getRequestBuilder()
                                  .entry(voice)
                                  .build();

        HttpPost post = getJsonPost(MixiURL.deleteEntry(), req);

        int c = count.incrementAndGet();
        log.info("request(" + c + ") : deleteEntry");
        return execute(post, jsonHandler(f.getResponseClass()));
    }

    public JsonBasicResponse deleteComment(Comment comment, User user) {
        DeleteCommentFactory f = Json.deleteCommentFactory();

        DeleteCommentRequest req = f.getRequestBuilder()
                                    .comment(comment)
                                    .user(user)
                                    .build();

        HttpPost post = getJsonPost(MixiURL.deleteComment(), req);

        int c = count.incrementAndGet();
        log.info("request(" + c + ") : deleteComment");
        return execute(post, jsonHandler(f.getResponseClass()));
    }

    public JsonBasicResponse deleteFavorite(Entry voice, User user, User viewer, String postKey) {
        DeleteFavoriteFactory f = Json.deleteFavoriteFactory();
        DeleteFavoriteRequest req = f.getRequestBuilder()
                                     .entry(voice)
                                     .favoriteUser(user)
                                     .viewer(viewer)
                                     .build();

        HttpPost post = getJsonPost(MixiURL.deleteFavorite(postKey), req);

        int c = count.incrementAndGet();
        log.info("request(" + c + ") : deleteFavorite");
        return execute(post, jsonHandler(f.getResponseClass()));
    }

    public GetPhotoThumbnailUrlResponse getPhotoThumbnailUrl(User viewer, User owner, String photoId, String postKey) {
        GetPhotoThumbnailUrlFactory f = Json.getPhotoThumbnailUrl();
        GetPhotoThumbnailUrlRequest req = f.getRequestBuilder()
                                           .ownerId(owner.getId())
                                           .memberId(viewer.getId())
                                           .photoId(photoId)
                                           .postKey(postKey)
                                           .build();
        HttpPost post = getJsonPost(MixiURL.getPhotoThumbnail(), req);

        int c = count.incrementAndGet();
        log.info("request(" + c + ") : getPhotoThumbnailUrl");
        GetPhotoThumbnailUrlResponse res =  execute(post, jsonHandler(f.getResponseClass()));
        return res;
    }

    public InputStream getImage(String url) {
        int c = count.incrementAndGet();
        log.info("request(" + c + ") : getImage");
        HttpResponse res = execute(new HttpGet(url));
        HttpEntity entity = res.getEntity();
        if(ImageType.get(entity.getContentType().getValue()) == null){
            throw new MixiVoiceException("画像ではありませんでした。");
        }
        Throwable cause;
        try {
            return entity.getContent();
        } catch (IllegalStateException e) {
            cause = e;
        } catch (IOException e) {
            cause = e;
        }
        throw new MixiVoiceException(cause);
    }

    public void close() {
        this.client.getConnectionManager().shutdown();
    }

    private HttpPost getJsonPost(String url, Object json) {
        HttpPost post = new HttpPost(url);
        post.setHeader("Content-Type", "application/json-rpc; charset=UTF-8");
        post.setEntity(getJsonEntity(json));

        return post;
    }

    private HttpResponse execute(HttpUriRequest request) {
        try {
            return this.client.execute(request);
        } catch (IOException e) {
            throw new MixiVoiceException(e);
        }
    }

    private <T> T execute(HttpUriRequest request, ResponseHandler<T> handler) {
        try {
            return this.client.execute(request, handler);
        } catch (IOException e) {
            throw new MixiVoiceException(e);
        }
    }

    private <T> T get(String url, ResponseHandler<T> handler) {
        T t = execute(new HttpGet(url), handler);
        return t;
    }

    protected <T> List<T> getList() {
        return new ArrayList<T>();
    }

    private HttpEntity getJsonEntity(Object json) {
        ContentProducer cp = new JsonContentProducer(json);
        return new EntityTemplate(cp);
    }

    private <T> JsonResponseHandler<T> jsonHandler(Class<T> clazz) {
        return new JsonResponseHandler<T>(clazz);
    }
}
